package aconcagua.measure

class MultipliedUnit extends CompoundUnit {
	private factors;

	// (class) instance creation
	public MultipliedUnit(aUnit, anotherUnit) {
		constructorHelper([aUnit, anotherUnit]);
	}
	
	public MultipliedUnit(ArrayList aFactors) {
		constructorHelper(aFactors);
	}
	
	// (class) instance creation - private
	private constructorHelper(aFactors) {
		factors = aFactors;
	}

	// accessing
	@Override
	public denominator() {
		return new NullUnit();
	}
	
	public factors() {
		return factors.clone();
	}

	@Override
	public nameForMany() {
		return this.factorsToStringDoing({ unit -> unit.nameForMany() });
	}

	@Override
	public nameForOne() {
		return this.factorsToStringDoing({ unit -> unit.nameForOne() });
	}

	@Override
	public numerator() {
		return this;
	}

	@Override
	public sign() {
		return this.factorsToStringDoing({ unit -> unit.sign() });
	}
	
	// accessing - private
	private factorsToStringDoing(aClosure) {
		def stream = new StringWriter();
		factors.each({ unit ->
			stream.write(aClosure.call(unit));
			stream.append('.') });
		return stream.toString();
		}
	
	// arithmetic operations
	@Override
	public divideByUnit(aUnit) {
		return aUnit.divideMultipliedUnit(this);
	}

	@Override
	public multiplyByUnit(aUnit) {
		return aUnit.multiplyMultipliedUnit(this);
	}

	// arithmetic operations - private
	@Override
	protected divide(DividedUnit aUnit) {
		return new DividedUnit(this, aUnit);
	}
	
	@Override
	protected divide(MultipliedUnit aUnit) {
		return new DividedUnit(this, aUnit);
	}
	
	@Override
	protected divide(NullUnit aUnit) {
		return new DividedUnit(aUnit, this);
	}
	
	@Override
	protected divide(SimpleUnit aUnit) {
		return new DividedUnit(this, aUnit);
	}
	
	@Override
	protected multiply(DividedUnit aUnit) {
		return new MultipliedUnit(aUnit, this);
	}
	
	@Override
	protected multiply(MultipliedUnit aUnit) {
		return new MultipliedUnit(aUnit, this);
	}
	
	@Override
	protected multiply(NullUnit aUnit) {
		return new MultipliedUnit(aUnit, this);
	}
	
	@Override
	protected multiply(SimpleUnit aUnit) {
		return new MultipliedUnit(aUnit, this);
	}
	
	// comparing
	@Override
	public boolean equals(Object anObject) {
		return (this.getClass() == anObject.getClass()
			&& this.hasSameFactorsAs(anObject));
		}

	@Override
	public int hashCode() {
		return factors.inject(0, { hash, unit -> hash.xor(unit.hashCode()) });
	}
	
	// testing - private
	private hasSameFactorsAs(aMultipliedUnit) {
		def factorsIntersection = aMultipliedUnit.factors();
		factors.each({ unit -> factorsIntersection.remove(unit) });
		return factorsIntersection.isEmpty();
	}
}
